/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.knolle.proveedorServicios;


import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.marcelo.agro.entidades.*;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Restrictions;
import org.marcelo.agro.constantes.IConstantes;
import org.marcelo.agro.core.GenericManager;
import org.marcelo.agro.core.api.IAccesoADatos;
import org.marcelo.comun.notificaciones.Notificaciones;

/**
 *
 * @author marcelo
 */
public abstract class DefaultDatabase<T>  implements IAccesoADatos<T>{

    private String schemaName = IConstantes.BBDD_schemaName;
    private String delimitadorColumna = IConstantes.BBDD_delimitadorColumna;
    private String delimitadorCaracter = IConstantes.BBDD_delimitadorCaracter;
    private String ecoding = IConstantes.BBDD_ecoding;
    
    public Properties DEFAULT_PROPERTIES ;
    public static final String DEFAULT_ORDEN = "id";
    
    
    //Valido solo para Derby no se para otro motor
    private static String PROCEDIMIENTO_IMPORT = "SYSCS_UTIL.SYSCS_IMPORT_TABLE";
    private static String PROCEDIMIENTO_EXPORT = "SYSCS_UTIL.SYSCS_EXPORT_TABLE";

    public DefaultDatabase() {
        DEFAULT_PROPERTIES = new Properties();
        DEFAULT_PROPERTIES.put("schemaName", schemaName);
        DEFAULT_PROPERTIES.put("delimitadorColumna", delimitadorColumna);
        DEFAULT_PROPERTIES.put("delimitadorCaracter", delimitadorCaracter);
        DEFAULT_PROPERTIES.put("encodig", ecoding);
    }
    
    
    @Override
    public void saveOrUpdate(T object) throws Exception{
        try{
            GenericManager.saveOrUpdate(object);
        }
        catch(HibernateException he){
           throw  new Exception(he.getMessage()); 
        }
        finally{
           
        }
    }
    
    @Override
    public void saveOrUpdates(Object[] objects) throws Exception{
            GenericManager.saveOrUpdates(objects);
    }

    
    @Override
    public List<T> search(T object) {
        try {
            return  GenericManager.searchObjectsLike(object, Order.asc(DEFAULT_ORDEN));
        }
        
        
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
        }
        return new ArrayList<T>(); 
    }

    @Override
    public List<TipoActividad> getTiposActividades() {
        try {
            return GenericManager.getAllObjects(TipoActividad.class, Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<TipoActividad>();
    }

    @Override
    public List<TipoProducto> getTiposProductos() {
        try{
            return GenericManager.getAllObjects(TipoProducto.class, Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<TipoProducto >();
    }

    @Override
    public List<UnidadMedidaDosis> getUnidadMedidaDosis() {
        try{
            return GenericManager.getAllObjects(UnidadMedidaDosis.class, Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<UnidadMedidaDosis>();
    }

    @Override
    public List<UnidadMedidaSuperficie> getUnidadesSuperficies() {
        try{
            return GenericManager.getAllObjects(UnidadMedidaSuperficie.class, Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<UnidadMedidaSuperficie >();
    }
    
    
    
    @Override
    public  List<T>getAll(Class clazz)  {
        try{
            return GenericManager.getAllObjects(clazz , Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<T>();
    }
    

    @Override
    public List<T> getAll(Class clazz,String... ordenarPorCampos) {
        Order[] orden = new Order[ordenarPorCampos.length];
        for(int  i = 0 ; i< ordenarPorCampos.length ; i++){
            orden[i] = Order.asc(ordenarPorCampos[i]);
        }
        try {
            return GenericManager.getAllObjects(clazz ,orden );
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<T>();
    }

    @Override
    public List<T> getAll(Class clazz,Integer inicio, Integer fin) {
        return getAll(clazz).subList(inicio, fin);
    }

    @Override
    public List<T> getAll(Class clazz,Integer inicio, Integer fin, String... ordenarPorCampos){
        return getAll(clazz, ordenarPorCampos).subList(inicio, fin);
    }

    @Override
    public List<TipoActividadXTipoProducto> getTipoProductosPorActividad() {
        try{
             return GenericManager.getAllObjects(TipoActividadXTipoProducto.class, Order.asc(DEFAULT_ORDEN));
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return new ArrayList<TipoActividadXTipoProducto>();
    }

    @Override
    public Integer getCount(Class<T> clazz) {
        try{
            return GenericManager.getAllObjects(clazz.getClass(), Order.asc(DEFAULT_ORDEN)).size();
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
        }
        return 0;
    }
    
    
     @Override
    public List<TipoProducto> getTiposProductos(TipoActividad ta) {
        if (ta == null) {
           return getTiposProductos();
        }
        
//        Para filtrar necesito los IDs de los elementos del que relacionan  y luego una consulta en la base de datos
//        para tener todos los id esos, estos IDs estaran en un array
        List<TipoActividadXTipoProducto>  tapps;
        List<TipoProducto> listResultado = new ArrayList<TipoProducto>();
        try{
            tapps = GenericManager.getFilteredObjects(
                    TipoActividadXTipoProducto.class, 
                    Arrays.asList(Restrictions.eq("tipoActividad", ta)), 
                    Order.asc("tipoProducto"));      
            
            for (TipoActividadXTipoProducto tipo : tapps ){
                 listResultado.add(tipo.getTipoProducto());
            }
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
         }
        return listResultado;        
    }

    @Override
    public String getVersionBBDD() {
        List<Verzion> versionList;
        try{
            versionList = GenericManager.getAllObjects(Verzion.class, Order.asc("id"));
            if (!versionList.isEmpty()) {
                return versionList.get(0).getVerzion();
            }
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
        }
        return "";
    }
     
    /**
     * La idea es pasarle el nombre de la tabla y los campos a insertar
     * por cada campo crear una variable posicional, luego hay que pasar los parametros
     * a ver que onda
     * @param columns
     * @return String con la consulta armada lista para y lista para recibir los parametros
     */
    protected String buildSQLInsert(String tableName, String[] columns) {
        StringBuilder sb = new StringBuilder();
        sb.append("INSERT INTO ");
        sb.append(tableName);
        sb.append(" (");
        //Solo por ahora las columnas estan conteniendo la version de la base de datos, para chequear la importacion
        //pero despues voy a mejorar este metodo
        for (int i =0 ; i < columns.length; i++ ) {
            sb.append(columns[i]);
            if (!(i == (columns.length - 1)))
                sb.append(",");
        }
        sb.append(") VALUES ( ");
        
        for (int i =0 ; i < columns.length ; i++  ) {
            sb.append("?");
            if (!(i == (columns.length - 1)))
                sb.append(",");
        }
        sb.append(" )");
        return sb.toString();
    }
    /**
     * La idea es pasarle el nombre del procedimineto y la cantidad de argumentos que recibira
     * para luego pasar al generic manager para que inserte los argumentos
     * @return un Strign que representa el comando
     */
    protected String buildCallProcedure(String procedure, int numArg) {
        StringBuilder sb = new StringBuilder();
        //Nombre del procedimiento
        //Nombre de los parametros
        //Tabla a exportar
        sb.append("CALL ");
        sb.append(procedure);
        sb.append(" ( ");
        for (int i = 0; i < numArg; i++) {
            sb.append("?");
            if (!(i == (numArg - 1)))
                sb.append(",");
        } 
        sb.append(" ) ");
        return sb.toString();
    }
    
   
    @Override
    public boolean saveCsvToTable(String tableName, File file)  throws Exception{
        boolean saved = false;
        
        String[] values = new String[7];
        //El primer parametro es el schema, para este caso particular es KNOLLE
        values[0] = schemaName.toUpperCase();
//        el Segundo es el nombre de la tabla
        values[1] = tableName.toUpperCase();
//        La ruta al archivo
        values[2] = file.getAbsolutePath();
//        caracter delimitacion columnas
        values[3] = delimitadorColumna;
//        Caracter delimitacion caracteres
        values[4] = delimitadorCaracter;
//        character set
        values[5] = ecoding;
//        Reemplazar los datos? 0 es agregar, 1 es sobreescribir
        values[6] = "0";
        String sqlCommand = buildCallProcedure(PROCEDIMIENTO_IMPORT, values.length);
        
        GenericManager.executeSql(sqlCommand, values);
        
        return saved;
        
    }
    
    @Override
    public boolean saveTableToCSV(String tableName, File file) throws Exception {
        boolean saved = false;
        
        String[] values = new String[6];
        //El primer parametro es el schema, para este caso particular es KNOLLE
        values[0] = schemaName.toUpperCase();
//        el Segundo es el nombre de la tabla
        values[1] = tableName.toUpperCase();
//        La ruta al archivo
        values[2] = file.getAbsolutePath();
//        caracter delimitacion columnas
        values[3] = delimitadorColumna;
//        Caracter delimitacion caracteres
        values[4] = delimitadorCaracter;
//        character set
        values[5] = ecoding;
        String sqlCommand = buildCallProcedure(PROCEDIMIENTO_EXPORT, values.length);
        GenericManager.executeSql(sqlCommand, values);
        
        return saved;
    }

    @Override
    public boolean saveCsvToTable(String tableName, File file, Properties properties) throws Exception {
        boolean saved = false;
        
        if (properties.isEmpty()) {
            properties = DEFAULT_PROPERTIES;
        }
        
        String[] values = new String[6];
        //El primer parametro es el schema, para este caso particular es KNOLLE
        values[0] = properties.getProperty(schemaName);
//        el Segundo es el nombre de la tabla
        values[1] = tableName.toUpperCase();
//        La ruta al archivo
        values[2] = file.getAbsolutePath();
//        caracter delimitacion columnas
        values[3] = properties.getProperty(delimitadorColumna);
//        Caracter delimitacion caracteres
        values[4] = properties.getProperty(delimitadorCaracter);
//        character set
        values[5] = properties.getProperty(ecoding);
         String sqlCommand = buildCallProcedure(PROCEDIMIENTO_EXPORT, values.length);
        
        GenericManager.executeSql(sqlCommand, values);
        return saved;
    }
    
    

    @Override
    public void refreshObject(Object object)  {
        try {
            GenericManager.refreshObject(object);
        }
        catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
        }
    }
    
    
    
    @Override
    public BigDecimal getTotalSuperficie()  {
       return getTotalSuperficie(null);
    }

    @Override
    public BigDecimal getTotalSuperficie(Campanas campana)  {
       String criterio = "SELECT SUM(lote.superficie) FROM Lotes lote";
       BigDecimal result ;
       if (campana != null) {
            criterio += " WHERE lote.id in (SELECT lpc.lotes.id FROM LotesPorCampana as lpc WHERE lpc.campanas.id = " +campana.getId() + " )";
       }
       List<BigDecimal> totalList;
       try{
           totalList = GenericManager.getListEnBaseACriterio(criterio);
           if (!totalList.isEmpty()) {
               result = totalList.get(0);
               if(result != null) {
                   return result;
               }
           }
       }
       catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return BigDecimal.ZERO;
       
    }

    @Override
    public BigDecimal getTotalSuperficieAlquilada() {
        return getTotalSuperfice(0);
    }

    @Override
    public BigDecimal getTotalSuperficiePropia() {
        return getTotalSuperfice(1);
    }
    
    
    private BigDecimal getTotalSuperfice(Integer propios){
        
        String criterio;
        
        List<BigDecimal> totalList;
        
        criterio = "SELECT SUM(lote.superficie) FROM Lotes lote where lote.esPropio = " + propios ;
        
        try{
            totalList = GenericManager.getListEnBaseACriterio(criterio);
           if (!totalList.isEmpty()) {
               if (totalList.get(0) == null){
                   return BigDecimal.ZERO;
               }
               else {
                   return totalList.get(0);
               }
           }
       }
       catch(Exception e){
           Notificaciones.notificar(MENSAJE_ERROR_CONSULTA,e.toString());
       }
       return BigDecimal.ZERO;
    }

    @Override
    public Boolean delete(Object object) throws Exception {
        Boolean result = Boolean.TRUE;
        GenericManager.delete(object);
        return result;
        
    }
    
    //Apartir de Aqui comienzan los metodos Estaticos
    
    /**
     * Accediendo a la clase GenericManager a traves de default Database,
     * Esto es para encapsular y separar la funcionalidad Generica del GenericManager
     * y de la Funcionalidad Especifica de DefaultDataBase
     * @param <T>
     * @param clazz
     * @param criterios
     * @param orders
     * @return 
     */
    
    
    
    protected static <T> List<T> getFilteredObjects(Class<T> clazz, List criterios, Order... orders){
        if (criterios == null) {
            criterios = new ArrayList();
        }
        try {
            return GenericManager.getFilteredObjects(clazz, criterios, orders);
        }
        catch(Exception e) {
            printError(e, "getFilteredObjects"); 
        }
        return new ArrayList<T>();
    }
    
    protected static <T>List<T> getAllObjects(Class<T> clazz, Order... orders){
        return getFilteredObjects(clazz, null, orders);
    }
    
    protected static <T> T getObjectById(Class<T> clazz, Integer id){
        try {
            return GenericManager.getObjectById(clazz, id);
        }
         catch (Exception e) { 
           printError(e, "getObjectById");
        }
        return null;
    }
    
    protected static void deleteParentAndChild(Object parent, Object[]... childs){
        try {
            GenericManager.deleteParentAndChild(parent, childs);
        }
         catch (Exception e) {
            printError(e, "deleteParentAndChild");
        }
    }
    
    protected static <T> List<T> searchObjectsLike(T t, Order... orders) {
        
        try {
            return GenericManager.searchObjectsLike(t, orders);
        }
         catch (Exception e) {
           printError(e, "searchObjectLike");
        }
        return new ArrayList<T>();
    }
    
    protected static <T> List<T>  getListEnBaseACriterio(String criterio){
        
        try {
            return GenericManager.getListEnBaseACriterio(criterio);
        }
         catch (Exception e) {
           printError(e, "getListEnBaseACriterio");
        }
        return new ArrayList<T>();
    
    }
    
    protected static <T0,T1> List<T0> getProjectionListObject(Class<T1> clazz, Projection proyection){
        
        try {
            return GenericManager.getProjectionListObjects(clazz, proyection);
        }
         catch (Exception e) {
            printError(e, "getProjectionListObject");
        }
        return new ArrayList<T0>();
        
    }
    
    /**
     * Este metodo sirve para devolver un nuevo 
     * @param <T>
     * @param objetoNoAdministrado
     * @param objetoAProyectar
     * @param projection
     * @param restricciones
     * @return Un Objeto del tipo T, o null en caso que no se encontro o produjo un error
     */
    protected static  <T> T getProjectionObject(Class objetoNoAdministrado,Class objetoAProyectar, Projection projection, List restricciones){
        try {
            return GenericManager.getProjectionObject(objetoNoAdministrado, objetoAProyectar, projection, restricciones);
        }
         catch (Exception e) {
            printError(e, "getProjectionObject");
        }
        return null;
    }
    
    
    protected static <T0,T1>  List<T0> getProjectionListObject(Class clazz, Projection proyection, List restrictions){
        
        try {
            return GenericManager.getProjectionListObject(clazz, proyection, restrictions);
        }
         catch (Exception e) {
            printError(e, "getProjectionListObject");
        }
        return null;
    }
    
    private static void printError(Exception e, String errorEn){
        Notificaciones.notificar(MENSAJE_ERROR_CONSULTA, errorEn + " " + e.toString());
        e.printStackTrace();
    }
    
}
